package com.ctrip.corp.obt.shard.core;

import static com.ctrip.corp.obt.generic.constants.GenericConstants.DEFAULT;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import com.ctrip.corp.obt.generic.utils.CollectionUtils;
import com.ctrip.corp.obt.shard.configuration.DruidDatabaseProperties;
import com.ctrip.corp.obt.shard.constants.DbRole;
import com.ctrip.corp.obt.shard.domain.ShardDataSourceMeta;

import com.ctrip.corp.obt.shard.exception.ShardException;
import com.ctrip.corp.obt.shard.infrastructure.etcd.EtcdShardSource;
import com.ctrip.corp.obt.shard.infrastructure.mapper.DataShardSourceDO;
import com.ctrip.corp.obt.shard.sql.mybatis.interceptor.pagination.Page;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;

/**
 * 数据源
 *
 * @author x.shen
 * @since 2024/1/30
 */
@Data
@Slf4j
@NoArgsConstructor
@Accessors(chain = true)
public class ShardDataSource {

    private String tenantId;

    private List<ShardDataSourceMeta> metas;

    private String source;

    public ShardDataSource(String tenantId, String source) {
        this.tenantId = tenantId;
        this.source = source;
    }

    public ShardDataSource(String tenantId, List<ShardDataSourceMeta> metas, String source) {
        this.tenantId = tenantId;
        this.metas = metas;
        this.source = source;
    }

    public void setMetas(List<ShardDataSourceMeta> metas) {
        this.metas = validate(metas) ? metas : Collections.emptyList();
    }

    /**
     * 构建默认数据源
     * 
     * @param dbProperties
     * @return
     */
    public static DataSource buildDefaultDatasource(DruidDatabaseProperties dbProperties) {
        return new ShardDataSourceMeta().buildMeta(dbProperties).buildDatasource(DEFAULT);
    }

    /**
     * etcd数据源构建
     *
     * @param etcdSource
     * @return
     */
    public static ShardDataSource build(EtcdShardSource etcdSource) {
        ShardDataSource dataSource = new ShardDataSource(etcdSource.getTenantId(), "etcd");

        // 兼容老逻辑
        if (CollectionUtils.isEmpty(etcdSource.getShardSourceMetas())) {
            ShardDataSourceMeta meta = new ShardDataSourceMeta(etcdSource.getDriverClassName(), etcdSource.getUrl(),
                etcdSource.getUsername(), etcdSource.getPassword(), Collections.emptyList());
            meta.setRole(DbRole.READ_WRITE);

            dataSource.setMetas(Collections.singletonList(meta));
            return dataSource;
        }

        dataSource.setMetas(etcdSource.getShardSourceMetas().stream().filter(Objects::nonNull)
            .map(etcd -> {
                ShardDataSourceMeta meta = new ShardDataSourceMeta(etcd.getDriverClassName(), etcd.getUrl(),
                    etcd.getUsername(), etcd.getPassword(), etcd.getEnablePlugins());
                meta.setRole(DbRole.getDbRoleByCode(etcd.getRole()));
                return meta;
            }).collect(Collectors.toList()));
        return dataSource;
    }

    /**
     * mybatis数据源构建 ---- 目前没啥用
     *
     * @param mybaitsSource
     * @return
     */
    public static ShardDataSource build(DataShardSourceDO mybaitsSource) {
        ShardDataSource dataSource = new ShardDataSource(mybaitsSource.getTenantId(), "mybaits");
        dataSource.setMetas(Collections.singletonList(new ShardDataSourceMeta(mybaitsSource.getDriverClassName(),
            mybaitsSource.getUrl(), mybaitsSource.getUsername(), mybaitsSource.getPassword(),
            Collections.emptyList())));
        return dataSource;
    }

    /**
     * 根据metas构建数据源datasource
     * 
     * @return
     */
    public Map<String, List<DataSource>> buildDataSourceCache() {
        Map<String, List<DataSource>> dataSourceMap = new HashMap<>();
        dataSourceMap.putIfAbsent(tenantId,
            metas.stream().map(meta -> meta.buildDatasource(tenantId)).collect(Collectors.toList()));
        return dataSourceMap;
    }

    /**
     * 根据metas获取enablePlugins
     * //todo 后续接入多数据源，需要修改
     *
     * @return
     */
    public Map<String, Set<String>> buildSqlPlugins() {
        Map<String, Set<String>> pluginsMap = new HashMap<>();
        Set<String> pluginsSet = metas.stream().filter(meta -> CollectionUtils.isNotEmpty(meta.getEnablePlugins()))
            .flatMap(meta -> meta.getEnablePlugins().stream()).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(pluginsSet)) {
            pluginsMap.putIfAbsent(tenantId, Collections.emptySet());
        } else {
            pluginsMap.putIfAbsent(tenantId, pluginsSet);
        }
        return pluginsMap;
    }

    /**
     * metas中数据源配置合法性检测，不满足应该报错，谨防业务中对库出现 误操作 / 无数据源 的情况
     * 
     * @param metas
     * @return
     */
    private boolean validate(List<ShardDataSourceMeta> metas) {
        if (CollectionUtils.isEmpty(metas)) {
            return false;
        }

        if (metas.size() == 1) {
            if (!DbRole.READ_WRITE.equals(metas.get(0).getRole())) {
                throw new ShardException("Only one data source, its role must be READ_WRITE: %s", tenantId);
            } else {
                return true;
            }
        }

        Set<DbRole> roles = metas.stream().map(ShardDataSourceMeta::getRole).collect(Collectors.toSet());
        if (roles.size() == 1 && !DbRole.READ_WRITE.equals(roles.iterator().next())) {
            throw new ShardException("if more than one dats source, their role can not be READ/READ or WRITE/WRITE: %s",
                tenantId);
        }
        return true;
    }
}
